/*-----------------------------------------------------------------------------------*/
/* Nuvoton Technology Corporation confidential                                      */
/*                                                                                   */
/* Copyright (c) 2008 by  Nuvoton Technology Corporation                             */
/* All rights reserved                                                               */
/*                                                                                   */
/*-----------------------------------------------------------------------------------*/
/* File Contents:                                                                    */
/*   i2c.c                                                                           */
/*                                                                                   */
/* This file contains:                                                               */
/*                                                                                   */
/* Project:                                                                          */
/*   1. 2007.07.23 modify for multi-master arbitration lost case.                    */
/*   2. 2008.07.14 Modify to W90P950 patform										 */
/*                                                                                   */
/*-----------------------------------------------------------------------------------*/ 
 
#ifdef ECOS 
#include "stdio.h"
#include "string.h"
#include "stdlib.h"
#include "drv_api.h"
#include "diag.h"
#include "wbio.h"
#else 
#include <stdio.h>
#include <string.h>
#include "wblib.h"
#endif

#include "NUC900_reg.h"
#include "NUC900_i2c.h"

/*-----------------------------------------*/
/* marco, type and constant definitions    */
/*-----------------------------------------*/
/*
	Define debug level
*/
//#define I2C_DEBUG
//#define I2C_DEBUG_PRINT_LINE
//#define I2C_DEBUG_ENABLE_ENTER_LEAVE
//#define I2C_DEBUG_ENABLE_MSG
//#define I2C_DEBUG_ENABLE_MSG2

#ifdef I2C_DEBUG
#ifdef ECOS
#define PDEBUG			diag_printf
#else
#define PDEBUG			sysprintf
#endif  /* ECOS */
#else
#define PDEBUG(fmt, arg...)
#endif  /* I2C_DEBUG */

#ifdef I2C_DEBUG_PRINT_LINE
#define PRN_LINE()				PDEBUG("[%-20s] : %d\n", __FUNCTION__, __LINE__)
#else
#define PRN_LINE()
#endif

#ifdef I2C_DEBUG_ENABLE_ENTER_LEAVE
#define ENTER()					PDEBUG("[%-20s] : Enter...\n", __FUNCTION__)
#define LEAVE()					PDEBUG("[%-20s] : Leave...\n", __FUNCTION__)
#else
#define ENTER()
#define LEAVE()
#endif

#ifdef I2C_DEBUG_ENABLE_MSG
#ifdef ECOS
#define MSG			diag_printf
#else
#define MSG			sysprintf
#endif  /* ECOS */
#else
#define MSG(msg)
#endif  /* I2C_DEBUG_ENABLE_MSG */

#ifdef I2C_DEBUG_ENABLE_MSG2
#ifdef ECOS
#define MSG2			diag_printf
#else
#define MSG2			sysprintf
#endif  /* ECOS */
#else
#define MSG2(...)
#endif  /* I2C_DEBUG_ENABLE_MSG2 */

#define i2c_out(dev, byte, addr)		outpw((dev)->base + addr, byte)
#define i2c_in(dev, addr)				inpw((dev)->base + addr)

#define i2cDisable(dev)		i2c_out(dev, 0x00, I2C_CSR)  /* Disable i2c core and interrupt */
#define i2cEnable(dev)		i2c_out(dev, 0x03, I2C_CSR)  /* Enable i2c core and interrupt  */
#define i2cIsBusFree(dev) ((i2c_in(dev, I2C_SWR) & 0x18) == 0x18 && (i2c_in(dev, I2C_CSR) & 0x0400) == 0) ? 1 : 0

/*-----------------------------------------*/
/* global file scope (static) variables    */
/*-----------------------------------------*/
typedef struct{
	INT32 base;		/* i2c bus number */
	INT32 openflag;
	volatile INT state;
	INT addr;
	UINT last_error;
	
	UINT subaddr;
	INT subaddr_len;

	UCHAR buffer[I2C_MAX_BUF_LEN];
	volatile UINT pos, len;

}i2c_dev;

static i2c_dev i2c_device[I2C_NUMBER];
static INT bNackValid;

#ifdef ECOS
cyg_interrupt  i2c_int;
cyg_handle_t   i2c_int_handle;
#endif

/*-----------------------------------------*/
/* prototypes of static functions          */
/*-----------------------------------------*/
static INT _i2cSetSpeed(i2c_dev *dev, INT sp);
static VOID _i2cCommand(i2c_dev *dev, INT cmd);
static VOID _i2cCalcAddr(i2c_dev *dev, INT mode);
static VOID _i2cReset(i2c_dev *dev);


//------------------------- Program -------------------------//
static INT _i2cSetSpeed(i2c_dev *dev, INT sp)  
{
	UINT d;

	if( sp != 100 && sp != 400)
		return(I2C_ERR_NOTTY);

	d = I2C_INPUT_CLOCK/(sp * 5) -1;

	i2c_out(dev, d & 0xffff, I2C_DIVIDER);

	MSG2("Set Speed = %d\n", sp);

	return 0;
}

static VOID _i2cCommand(i2c_dev *dev, INT cmd)
{
	bNackValid = (cmd & I2C_CMD_WRITE) ? 1 : 0;
	i2c_out(dev, cmd, I2C_CMDR);

	return;
}

static VOID _i2cCalcAddr(i2c_dev *dev, INT mode)
{
	INT i;
	UINT subaddr;

	subaddr = dev->subaddr;

	dev->buffer[0] = (((dev->addr << 1) & 0xfe) | I2C_WRITE) & 0xff;

	for(i = dev->subaddr_len; i > 0; i--){
		dev->buffer[i] = subaddr & 0xff;
		subaddr >>= 8;
	}

	if(mode == I2C_STATE_READ){
		i = dev->subaddr_len + 1;
		dev->buffer[i] = (((dev->addr << 1) & 0xfe)) | I2C_READ;
	}

	return;		
}

/* init i2c_dev after open */
static VOID _i2cReset(i2c_dev *dev)  
{
	dev->addr = -1;
	dev->last_error = 0;
	dev->subaddr = 0;
	dev->subaddr_len = 0;

	_i2cSetSpeed(dev, 100);
	
	return;	
}

#ifdef ECOS
cyg_uint32 i2cISR (cyg_vector_t vector, cyg_addrword_t data)
#else
VOID i2cISR(VOID)  
#endif
{
	int csr, val;
	UINT32 uReg;
	i2c_dev *dev;
	
	MSG2("****\n");
	uReg = inpw(REG_AIC_GASR);
	if( uReg & (1 << 26) )  /* I2C0 */
		dev = (i2c_dev *) ( (UINT32)&i2c_device[0] );
	else                    /* I2C1 */
		dev = (i2c_dev *) ( (UINT32)&i2c_device[1] );
	
	csr = i2c_in(dev, I2C_CSR);
	csr |= 0x04;

	i2c_out(dev, csr, I2C_CSR);  /* clear interrupt flag */

#ifdef ECOS
	if(dev->state == I2C_STATE_NOP)
	{
		cyg_drv_interrupt_acknowledge(vector);
		return CYG_ISR_HANDLED;
	}
#else	
	if(dev->state == I2C_STATE_NOP)
		return;
#endif		

	MSG2("bNackValid = %d\n", bNackValid);

	if((csr & 0x800) && bNackValid){	/* NACK only valid in WRITE */
		MSG("NACK Error\n");
		dev->last_error = I2C_ERR_NACK;
		_i2cCommand(dev, I2C_CMD_STOP);
		dev->state = I2C_STATE_NOP;		
	}
	else if(csr & 0x200){	            /* Arbitration lost */
		MSG("Arbitration lost\n");
		dev->last_error = I2C_ERR_LOSTARBITRATION;
		//_i2cCommand(dev, I2C_CMD_STOP);  // 2007.07.23 mark, NS22 HHWu.    Should not be set.
		dev->state = I2C_STATE_NOP;
	}
	else if(!(csr & 0x100)){		    /* transmit complete */
		if(dev->pos < dev->subaddr_len + 1){	/* send address state */
			MSG("Send Address\n");
			val = dev->buffer[dev->pos++] & 0xff;
			i2c_out(dev, val, I2C_TxR);
			_i2cCommand(dev, I2C_CMD_WRITE);
		}
		else if(dev->state == I2C_STATE_READ){	

			/* sub address send over , begin restart a read command */

			if(dev->pos == dev->subaddr_len + 1){

				MSG("Restart Reading...\n");

				val = dev->buffer[dev->pos++];
				i2c_out(dev, val, I2C_TxR);
				_i2cCommand(dev, I2C_CMD_START | I2C_CMD_WRITE);
			}
			else{

				dev->buffer[dev->pos++] = i2c_in(dev, I2C_RxR) & 0xff;

				MSG2("Read Pos : [%02x]   Data : [%02x]\n",
						dev->pos, dev->buffer[dev->pos-1]);

				if( dev->pos < dev->len){
					MSG("Read Next\n");
					if(dev->pos == dev->len -1 )	/* last character */
						_i2cCommand(dev, I2C_CMD_READ |
										I2C_CMD_STOP |
										I2C_CMD_NACK);
					else
						_i2cCommand(dev, I2C_CMD_READ);
				}
				else{
					MSG("Read Over \n");
					dev->state = I2C_STATE_NOP;
				}
			}
		}
		else if(dev->state == I2C_STATE_WRITE){	/* write data */
					
			if( dev->pos < dev->len){
				MSG2("Write Pos : [%02x]   Data : [%02x]\n",
						dev->pos, dev->buffer[dev->pos]);

				val = dev->buffer[dev->pos];

				i2c_out(dev, val, I2C_TxR);
			
				if(dev->pos == dev->len -1 )	/* last character */
					_i2cCommand(dev, I2C_CMD_WRITE| I2C_CMD_STOP);
				else
					_i2cCommand(dev, I2C_CMD_WRITE);
		
				dev->pos ++;
			}
			else{
				MSG("Write Over\n");
				dev->state = I2C_STATE_NOP;
			}
		}
	}

#ifdef ECOS
	cyg_drv_interrupt_acknowledge(vector);
	return CYG_ISR_HANDLED;
#else
	return;
#endif	
}

/*-----------------------------------------------------------------------------------*/
/* Function:                                                                         */
/*   i2cOpen                                                                         */
/*                                                                                   */
/* Parameters:                                                                       */
/*   param             Interface number.                                             */
/*                                                                                   */                                                                           
/* Returns:                                                                          */
/*   0                 Success.                                                      */
/*   I2C_ERR_BUSY      Interface already opened.                                     */
/*   I2C_ERR_NODEV     Interface number out of range.                                */
/*                                                                                   */
/* Side effects:                                                                     */
/*   None.                                                                           */
/*                                                                                   */
/* Description:                                                                      */
/*   This function reset the i2c interface and enable interrupt.                     */
/*                                                                                   */
/*-----------------------------------------------------------------------------------*/

INT32 i2cOpen(PVOID param)  
{
	i2c_dev *dev;

	if( (UINT)param >= I2C_NUMBER)
		return I2C_ERR_NODEV;

	dev = (i2c_dev *)((UINT)&i2c_device[(UINT)param] );

	if( dev->openflag != 0 )		/* a card slot can open only once */
		return(I2C_ERR_BUSY);
			
	/* Import enable two I2C clock together */
	outpw(REG_CLKEN, inpw(REG_CLKEN) | 0xC0000000);
			
	memset(dev, 0, sizeof(i2c_dev));
	dev->base = ((UINT)param) ? I2C1_BA : I2C0_BA;
	
	_i2cReset(dev);
	
	outpw(REG_AIC_GEN, inpw(REG_AIC_GEN) | 0x0C000000);  /* Important enable I2C interrupt group */
	
#ifdef ECOS
	cyg_drv_interrupt_unmask(CYGNUM_HAL_INTERRUPT_I2C_GROUP);
#else	
	sysEnableInterrupt(IRQ_I2C_GROUP);
#endif	
	
	dev->openflag = 1;
	
	return 0;
}

/*-----------------------------------------------------------------------------------*/
/* Function:                                                                         */
/*   i2cClose                                                                        */
/*                                                                                   */
/* Parameters:                                                                       */
/*   fd			    I2C interface number.                                            */
/*                                                                                   */                                                                           
/* Returns:                                                                          */
/*   0				Success.                                                         */
/*	 SC_ENODEV      Interface number out of range.                                   */
/*                                                                                   */
/* Side effects:                                                                     */
/*   None.                                                                           */
/*                                                                                   */
/* Description:                                                                      */
/*   Disable I2C interrupt. And initialize some parameters.                          */
/*                                                                                   */
/*-----------------------------------------------------------------------------------*/

INT32 i2cClose(INT32 fd)  
{
	i2c_dev *dev;

	if(fd != 0 && fd != 1)
		return(I2C_ERR_NODEV);
		
	dev = (i2c_dev *)( (UINT32)&i2c_device[fd] );	
		
	dev->openflag = 0;	
	
#ifdef ECOS	
	cyg_drv_interrupt_mask(CYGNUM_HAL_INTERRUPT_I2C_GROUP);	
#else		
	sysDisableInterrupt(IRQ_I2C_GROUP);
#endif	

	return 0;
}

/*-----------------------------------------------------------------------------------*/
/* Function:                                                                         */
/*   i2cRead                                                                         */
/*                                                                                   */
/* Parameters:                                                                       */
/*   fd          I2C interface number.                                               */
/*   buf         Receive buffer pointer.                                             */
/*   len         Receive buffer length.                                              */
/*                                                                                   */
/* Returns:                                                                          */
/*   > 0                      Retrun read length on success.                         */
/*   I2C_ERR_BUSY             Interface busy.                                        */
/*   I2C_ERR_IO               Interface not opened.                                  */
/*   I2C_ERR_NODEV            No such device.                                        */
/*   I2C_ERR_NACK             Slave returns an errorneous ACK.                       */
/*   I2C_ERR_LOSTARBITRATION                                                         */
/*                                                                                   */
/* Side effects:                                                                     */
/*   None.                                                                           */
/*                                                                                   */
/* Description:                                                                      */
/*   Read data from I2C slave.                                                       */
/*                                                                                   */
/*-----------------------------------------------------------------------------------*/

INT32 i2cRead(INT32 fd, PUINT8 buf, UINT32 len)
{
	i2c_dev *dev;
	
	if(fd != 1 && fd != 0)
		return(I2C_ERR_NODEV);
		
	dev = (i2c_dev *)( (UINT32)&i2c_device[fd] );	
	
	if(dev->openflag == 0)
		return(I2C_ERR_IO);			

	if(len == 0)
		return 0;

	if(!i2cIsBusFree(dev))
		return(I2C_ERR_BUSY);
		
	if(len > I2C_MAX_BUF_LEN - 10)
		len = I2C_MAX_BUF_LEN - 10;

	dev->state = I2C_STATE_READ;
	dev->pos = 1;
	                                            /* Current ISR design will get one garbage byte */
	dev->len = dev->subaddr_len + 1 + len + 2;  /* plus 1 unused char                           */ 
	dev->last_error = 0;

	_i2cCalcAddr(dev, I2C_STATE_READ);

	i2cEnable(dev);
	
	i2c_out(dev, dev->buffer[0] & 0xff, I2C_TxR);

// HHWu addb    2007.07.23    Multi-mater issue.
	if(!i2cIsBusFree(dev))
		return(I2C_ERR_BUSY);
// HHWu adde

	_i2cCommand(dev, I2C_CMD_START | I2C_CMD_WRITE);

	while(dev->state != I2C_STATE_NOP);

	i2cDisable(dev);

	if(dev->last_error)
		return(dev->last_error);
	
	memcpy(buf, dev->buffer + dev->subaddr_len + 3, len);

	dev->subaddr += len;
	
	return len;
}

/*-----------------------------------------------------------------------------------*/
/* Function:                                                                         */
/*   i2cWrite                                                                        */
/*                                                                                   */
/* Parameters:                                                                       */
/*   fd          I2C interface number.                                               */
/*   buf         Transmit buffer pointer.                                            */
/*   len         Transmit buffer length.                                             */
/*                                                                                   */                                                                           
/* Returns:                                                                          */
/*      > 0                      Return write length on success.                     */
/*      I2C_ERR_BUSY             Interface busy.                                     */
/*      I2C_ERR_IO               Interface not opened.                               */
/*      I2C_ERR_NODEV            No such device.                                     */
/*      I2C_ERR_NACK             Slave returns an errorneous ACK.                    */
/*      I2C_ERR_LOSTARBITRATION                                                      */
/*                                                                                   */
/* Side effects:                                                                     */
/*   None.                                                                           */
/*                                                                                   */
/* Description:                                                                      */
/*   Write data to i2c slave.                                                        */
/*                                                                                   */
/*-----------------------------------------------------------------------------------*/

INT32 i2cWrite(INT32 fd, PUINT8 buf, UINT32 len)
{
	i2c_dev *dev;
	
	if(fd != 1 && fd != 0)
		return(I2C_ERR_NODEV);

	dev = (i2c_dev *)( (UINT32)&i2c_device[fd] );	
			
	if(dev->openflag == 0)
		return(I2C_ERR_IO);			

	if(len == 0)
		return 0;

	if(!i2cIsBusFree(dev))
		return(I2C_ERR_BUSY);
		
	if(len > I2C_MAX_BUF_LEN - 10)
		len = I2C_MAX_BUF_LEN - 10;

	memcpy(dev->buffer + dev->subaddr_len + 1 , buf, len);

	dev->state = I2C_STATE_WRITE;
	dev->pos = 1;
	dev->len = dev->subaddr_len + 1 + len;
	dev->last_error = 0;

	_i2cCalcAddr(dev, I2C_STATE_WRITE);
	
	i2cEnable(dev);
	
	i2c_out(dev, dev->buffer[0] & 0xff, I2C_TxR);

// HHWu addb    2007.07.23    Multi-mater issue.
	if(!i2cIsBusFree(dev))
		return(I2C_ERR_BUSY);
// HHWu adde

	_i2cCommand(dev, I2C_CMD_START | I2C_CMD_WRITE);

	while(dev->state != I2C_STATE_NOP);

	i2cDisable(dev);

	if(dev->last_error)
		return(dev->last_error);
	
	dev->subaddr += len;
	
	return len;
}

/*-----------------------------------------------------------------------------------*/
/* Function:                                                                         */
/*   i2cIoctl                                                                        */
/*                                                                                   */
/* Parameters:                                                                       */
/*   fd                Interface number.                                             */
/*   cmd               Command.                                                      */
/*   arg0, arg1        Arguments for the command.                                    */   
/*                                                                                   */                                                                           
/* Returns:                                                                          */
/*   0                 Success                                                       */
/*   I2C_ERR_NODEV     Interface number out of range.                                */
/*   I2C_ERR_IO        Card not activated or card removed.                           */
/*   I2C_ERR_NOTTY     Command not support, or parameter error.                      */
/*                                                                                   */
/* Side effects:                                                                     */
/*   None.                                                                           */
/*                                                                                   */
/* Description:                                                                      */
/*   Support some I2C driver commands for application.                               */
/*                                                                                   */
/*-----------------------------------------------------------------------------------*/

INT32 i2cIoctl(INT32 fd, UINT32 cmd, UINT32 arg0, UINT32 arg1)
{
	i2c_dev *dev;
	
	if(fd != 0 && fd != 1)
		return(I2C_ERR_NODEV);

	dev = (i2c_dev *)((UINT)&i2c_device[fd] );
	if(dev->openflag == 0)
		return(I2C_ERR_IO);	

	switch(cmd){
		case I2C_IOC_SET_DEV_ADDRESS:
			dev->addr = arg0;
			MSG2("Address : %02x\n", arg0&0xff);
			break;
			
		case I2C_IOC_SET_SPEED:

			return(_i2cSetSpeed(dev, (INT)arg0));

		case I2C_IOC_SET_SUB_ADDRESS:

			if(arg1 > 4){
				return(I2C_ERR_NOTTY);
			}

			dev->subaddr = arg0;
			dev->subaddr_len = arg1;
			MSG2("Sub Address = %02x, length = %d\n",arg0, arg1);
						
			break;

		default:
			return(I2C_ERR_NOTTY);
	}

	return (0);	
}

/*-----------------------------------------------------------------------------------*/
/* Function:                                                                         */
/*   i2cExit                                                                         */
/*                                                                                   */
/* Parameters:                                                                       */
/*   None.                                                                           */
/*                                                                                   */                                                                           
/* Returns:                                                                          */
/*   0                 Success.                                                      */
/*                                                                                   */
/* Side effects:                                                                     */
/*   None.                                                                           */
/*                                                                                   */
/* Description:                                                                      */
/*   Do nothing.                                                                     */
/*                                                                                   */
/*-----------------------------------------------------------------------------------*/

INT32 i2cExit(VOID)
{
	return(0);
}

/*-----------------------------------------------------------------------------------*/
/* Function:                                                                         */
/*   i2cInit                                                                         */
/*                                                                                   */
/* Parameters:                                                                       */
/*   None.                                                                           */
/*                                                                                   */                                                                           
/* Returns:                                                                          */
/*   0        Success.                                                               */
/*                                                                                   */
/* Side effects:                                                                     */
/*   None.                                                                           */
/*                                                                                   */
/* Description:                                                                      */
/*   Configure GPIO to I2C mode, install ISR.                                        */
/*                                                                                   */
/*-----------------------------------------------------------------------------------*/

INT32  i2cInit(VOID)  
{
	/* Configure GPIO pins to I2C mode */
	outpw(REG_MFSEL, inpw(REG_MFSEL) | 0x14000);
	
#ifdef ECOS
	cyg_drv_interrupt_create(CYGNUM_HAL_INTERRUPT_I2C_GROUP, 99, 0, i2cISR, NULL, &i2c_int_handle, &i2c_int);
    cyg_drv_interrupt_attach(i2c_int_handle);    
#else	
	sysInstallISR(IRQ_LEVEL_1, IRQ_I2C_GROUP, (PVOID)i2cISR);	
	sysSetLocalInterrupt(ENABLE_IRQ);
#endif	
	
	memset(i2c_device, 0, sizeof(i2c_device));	
	
	return(0);
}
